require 'calibre/dictionary'
require(File.dirname(__FILE__) + '/config.rb');

class Shl
	include DefaultShlConfig
	attr_reader :textpos
	
	SHL_LANG = File.dirname(__FILE__) + '/lang/'
	SHL_OUT = File.dirname(__FILE__) + '/out/'

	def initialize(language, options = P_DEFAULT)
		@language = language.downcase
		# puts "******* LANGUAGE #{@language}"
		require SHL_LANG + @language + '_lang.rb'
		className = class_name(@language + '_lang')
		eval "@lang = #{className}.new"
		
		@errors = 0
		
		# internal language structures initialization
		@_states = Hash.new
		@_flags = Hash.new
		@_data = Hash.new
		@_class = Hash.new
		@_delim = Hash.new
		@_trans = Hash.new
		
		j = 0
		@lang.states.each do |state, state_hash|
			if state != P_QUIT_STATE
				@_states[state] = j
				j += 1
			end
		end
		
		@_states[P_RET_STATE] = @_ret = j
		@_states[P_QUIT_STATE] = @_quit = j+1
		
		@lang.states.each do |state, state_hash|
			# puts ">>" + state
			state = @_states[state]
			@_flags[state] = state_hash[XL_FLAGS]
			@_data[state] = state_hash[XL_DATA]
			@_class[state] = state_hash[XL_CLASS]
			
			
			if state_hash[XL_DIAGR].class == Dictionary
				@_delim[state] = Array.new
				@_trans[state] = Dictionary.new
				
				# puts state_hash
				
				# walk through diagram hash
				state_hash[XL_DIAGR].each do |delimiter, trans_array|
					# puts "  >> " + delimiter
					@_delim[state] << delimiter
					# convert state from string to integer
					trans_array[XL_DSTATE] = @_states[trans_array[XL_DSTATE]]
					@_trans[state][delimiter] = trans_array
				end
			else
				@_delim[state] = nil
				@_trans[state] = nil
			end
		end
		
		# puts @_delim
		
		@lang.initial_state = @_states[@lang.initial_state]
	end
	
	def highlight_string(output, text, offset = 0)
		if(output.class == String)
			file = output + '_output'
			className = class_name(file)
			require SHL_OUT + file + '.rb'
			eval "@output = #{className}.new"
		else
			@output = output
		end
		
		#initialize properties
		@text = text
		@textlen = text.length
		@textpos = offset
		@out = ''
		
		parse_string(@lang.initial_state)
		#@out << @output.template_end
		
		return @out
		
	end
	
	def parse_string (state)
		while !(word = getword(@_delim[state])).nil?
	
			if(word.class == Array)	#podla typu vystupu getword() vieme, ci bol delimiter.
				# puts 'NEWSTATE = ' + word[0]
				newstate = @_trans[state][word[0]][XL_DSTATE]	#nacitaj novy stav
				if DEBUG_STATE
					@out << "[newstate:#{newstate}]" 
				end
				
				if newstate == @_ret
					# rozhodujeme, ci sa slovo spracuje uz teraz, alebo az po navrate z rekurzie
					# =0 - aplikuje sa stary stav na toto slovo,
					# =1 - slovo sa vrati spet do streamu.
					# puts 'DTYPE = ' + @_trans[state][word[0]][XL_DTYPE].to_s
					if(@_trans[state][word[0]][XL_DTYPE] == 1)
						if DEBUG_STATE
							@out << "[retb]"
						end
						# puts 'RETB = ' + word[1]
						@textpos -= word[1].length
					else
						if DEBUG_STATE
							@out << "[ret]"
						end
						
						@out << @output.template(word[1], @_class[state])
					end
					
					return
				end
				
				# rozhodujeme, ci sa zmena stavu aplikuje na toto, alebo az dalsie slovo
				# =0 - aplikuje sa na toto slovo, 
				# =1 - aplikuje sa dalsie slovo.
				# =-1 - char back to stream
				if(@_trans[state][word[0]][XL_DTYPE] == 1)			#if ...[XL_DTYPE] == 1
					@out << @output.template(word[1],@_class[state])
				else
					if(@_trans[state][word[0]][XL_DTYPE] < 0)
						@textpos -= word[1].length;
					else
						@out << @output.template(word[1],@_class[newstate])
					end
				end

				if((@_flags[newstate] & PF_NEWLANG) != 0)
					if(newstate == @_quit)
						if DEBUG_STATE
							@out << "[quit]"
						end
						return
					end
					
					if DEBUG_STATE
						newlangnam=@_data[newstate]
						@out << "[new:#{newlangnam}]"
					end
					
					newparser = Shl.new(@_data[newstate], @options)
					
					@out << newparser.highlight_string(@output, @text, @textpos)
					
					@textpos = newparser.textpos
					
					if !@_trans[newstate].nil?		#ak existuju prechody, stav nie je regularny a aplikuje sa zmena stavu
						state=newstate
					end
					
					next
					
				end
				if((@_flags[newstate] & PF_RECURSION) != 0)	#ak je flag rekurzia, volaj stav rekurentne
					if(state!=newstate)		#zabranenie vnoreniu rekurzie do rekurzie
						parse_string newstate
						next
					end
				end
				state=newstate		#inak len zmen stav
			else
				# Sem sa dostaneme, ak nacitane slovo nebolo delimiter.
				# word nie je array, tym padom s nim pracujeme ako obycajne.
				if((@_flags[state] & PF_KEYWORD) != 0)
					if !@lang.keywords.nil? and !@lang.keywords[1][word].nil?
						@out << @output.template(word, @lang.keywords[0] << @lang.keywords[1][word])
					else
						@out << @output.template(word,@_class[state])
					end
				else
					@out << @output.template(word,@_class[state])
				end
				if DEBUG_STATE
					@out << "," << state << ","
				end
			end
		end #END while()
	end
	
	def get_group_delim(del)
		ch = @text[@textpos,1]
		case del
		
			#SPACE
			when "SPACE"
				return [ del, ch ] if !ch.match(/[ \t\n\r]+/).nil?
			when "!SPACE"
				return [ del, ch ] if ch.match(/[ \t\n\r]+/).nil?
				
			#NUMBER
			when "NUMBER"
				return [ del, ch ] if !ch.match(/[0-9]+/).nil?
			when "!NUMBER"
				return [ del, ch ] if ch.match(/[0-9]+/).nil?
			
			#ALPHA
			when "ALPHA"
				return [ del, ch ] if !ch.match(/[A-z]+/).nil?
			when "!ALPHA"
				return [ del, ch ] if ch.match(/[A-z]+/).nil?
				
			#ALPHANUMERIC
			when "ALNUM"
				return [ del, ch ] if !ch.match(/[A-z0-9]+/).nil?
			when "!ALNUM"
				return [ del, ch ] if ch.match(/[A-z0-9]+/).nil?
				
			#HEXNUM
			when "HEXNUM"
				return [ del, ch ] if !ch.match(/[0-9a-fA-F]+/).nil?
			when "!HEXNUM"
				return [ del, ch ] if ch.match(/[0-9a-fA-F]+/).nil?
				
			#ALL
			when "ALL"
				return [ del, ch ]
				
			#COUNTAB
			when "_COUNTAB"
				return [ del, ch ] if !ch.match(/\n\t/).nil?
				
			#SAFECHAR 
			when "SAFECHAR"
				return [ del, ch ] if !ch.match(/[A-z_0-9]+/).nil?
			when "!SAFECHAR"
				return [ del, ch ] if ch.match(/[A-z_0-9]+/).nil?
				
			#PHP_DELIM"
			when "PHP_DELIM"
				return [ del, ch ] if !ch.match(/[ \t\n\r\;\,\:\(\)\{\}\[\]\!\=\%\&\|\+\-\*\/]+/).nil?
		end
		return false
	end
	
	def is_delimiter(delim)
		delim.each do |del|
			if GROUP_DELIMITERS.include?(del)
				result = get_group_delim(del)
				return result if result.class == Array
			end
			if del == @text[@textpos,(del.length)]
				# puts del
				return [ del, del ]
			else
				# puts ">" + @text[@textpos,(del.length)] + "<" + " ... " + del
			end
		end
		return false
	end
	
	def getword(delim)
		# puts delim
		result = ''
		if (@textpos < @textlen)
			del = is_delimiter(delim)
			#ak sme nasli delimiter
			if del != false
				#posunieme poziciu o delimiter dalej
				@textpos += del[1].length
				# puts 'D= ' + del[1]
				#vratime pole
				return del
			else
				#inak naberieme do stringu vsetok text az po prvy ocakavany delimiter
				while (@textpos < @textlen) and (is_delimiter(delim) == false)
					result += @text[@textpos, 1]
					@textpos += 1
				end
				# puts 'R= ' + result
			end
		end
		
		return nil if result == ''
		# vratime string
		return result
	end
	
	def put_error error
		if DEBUG_REPORT
			puts 'SHL Parser: ' + error
		end
		@errors += 1
	end
	
	#helper
	def class_name(file_name)
		className = ''
		file_name.split('_').each do |word|
			className += word.capitalize
		end
		return className
	end

end